﻿using Microsoft.Win32;
using SolidWorks.Interop.sldworks;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text.RegularExpressions;

namespace Paine.SolidWorks.Base
{
    /// <summary>
    /// 用于Exe连接SolidWorks
    /// </summary>
    public class Connect
    {
        [DllImport("oleaut32.dll")]
        private static extern int GetActiveObject(ref Guid classId, IntPtr pvReserved, [MarshalAs(UnmanagedType.IUnknown)] ref object ppunk);

        [DllImport("ole32.dll")]
        private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);

        /// <summary>
        /// 获取正在运行的SolidWorks对象
        /// </summary>
        /// <param name="version">SolidWorks版本值 </param>
        /// <returns>正在运行的SolidWorks对象</returns>
        public static SldWorks GetRunningSolidWorks(int version = -1)
        {
            object app = null;

            var Guid = version == -1 ? Type.GetTypeFromProgID("SldWorks.Application").GUID : Type.GetTypeFromProgID("SldWorks.Application." + version.ToString()).GUID;

            GetActiveObject(ref Guid, IntPtr.Zero, ref app);

            return (SldWorks)app;
        }

        /// <summary>
        /// 获取正在运行的SolidWorks对象
        /// </summary>
        /// <param name="version">SolidWorks版本年份 </param>
        /// <returns>正在运行的SolidWorks对象</returns>
        public static SldWorks GetRunningSolidWorks(SolidWorksVersion version)
        {
            return GetRunningSolidWorks((int)version);
        }

        ///<summary>
        ///创建SolidWorks并返回实例 返回SldWorks
        ///</summary>
        ///<param name="visible">实例可见性.</param>
        ///<param name="version">版本(26 = sw 2018), 默认为-1 表示默认版本.</param>
        ///<returns>SldWorks对象</returns>
        public static SldWorks CreateSolidWorks(bool visible, int version = -1)
        {
            SldWorks app;

            if (version == -1)
                app = (SldWorks)Activator.CreateInstance(Type.GetTypeFromProgID("SldWorks.Application"));
            else
                app = (SldWorks)Activator.CreateInstance(Type.GetTypeFromProgID("SldWorks.Application." + version.ToString()));

            if (app != null)
                app.Visible = visible;
            return app;
        }

        ///<summary>
        ///创建SolidWorks并返回实例 返回SldWorks
        ///</summary>
        ///<param name="visible">实例可见性.</param>
        ///<param name="version">版本(26 = sw 2018), 默认为-1 表示默认版本.</param>
        ///<returns>SldWorks对象</returns>
        public static SldWorks CreateSolidWorks(bool visible, SolidWorksVersion version = SolidWorksVersion.Default)
        {
            return CreateSolidWorks(visible, (int)version);
        }

        /// <summary>
        /// 查看是否在Solidworks进程存在，可以选择关闭进程
        /// </summary>
        /// <param name="killAll">是否关闭SolidWorks进行</param>
        /// <param name="isMainWindowCreated">是否有主窗口进程.</param>
        /// <returns></returns>
        public static bool IsRunning(bool killAll = false, bool isMainWindowCreated = false)
        {
            var swProcess = Process.GetProcessesByName("SLDWORKS");

            if (swProcess.Length == 0)
                return false;

            if (killAll)
            {
                foreach (var myProcess in swProcess)
                    myProcess.Kill();
            }

            if (!isMainWindowCreated) return true;
            {
                foreach (var myProcess in swProcess)
                {
                    if (myProcess.MainWindowHandle == IntPtr.Zero)
                        return false;
                }
            }

            return true;
        }

        /// <summary>
        /// 根据版本获取solidworks路径
        /// </summary>
        /// <param name="version"></param>
        /// <returns></returns>
        public static string GetSolidWorksExecutablePath(int version)
        {
            using (var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
            {
                if (version == -1)
                {
                    using (RegistryKey swKeys = hklm.OpenSubKey(@"SOFTWARE\SolidWorks"))
                    {
                        if (swKeys != null)
                        {
                            var keys = swKeys.GetSubKeyNames();
                            foreach (var key in keys)
                            {
                                if (key.IndexOf("SOLIDWORKS ", StringComparison.Ordinal) != -1)
                                {
                                    string[] split = key.Split(' ');
                                    if (int.TryParse(split[1], out var retVal))
                                        version = ConvertYearToMajorVersion(retVal);
                                }
                            }
                        }
                    }
                }

                using (RegistryKey key = hklm.OpenSubKey(@"SOFTWARE\SolidWorks\SOLIDWORKS " + ConvertMajorVersionToYear(version).ToString() + @"\Setup"))
                {
                    if (key == null)
                        return null;
                    else
                        return key.GetValue("SolidWorks Folder") + "SLDWORKS.EXE";
                }
            }
        }

        /// <summary>
        /// 转换年份到版本
        /// </summary>
        /// <param name="year"></param>
        /// <returns></returns>
        public static int ConvertYearToMajorVersion(int year)
        {
            return year - 1992;
        }

        /// <summary>
        /// 转换版本到年份
        /// </summary>
        /// <param name="version"></param>
        /// <returns></returns>
        public static int ConvertMajorVersionToYear(int version)
        {
            return version + 1992;
        }

        /// <summary>
        /// 创建一个新的Solidworks并返回实例
        /// </summary>
        /// <param name="version">指定版本号，-1表示默认.</param>
        /// <param name="visible"></param>
        /// <param name="suppressDialogs">True 则禁用solidworks弹出消息.</param>
        /// <param name="requireMainWindow">True 表示运行完显示到主窗口</param>
        /// <param name="startProcessTimeout">返回Null 如果SolidWorks在指定时间内没有打开。</param>
        /// <param name="createWindowTimeout">返回Null 如果SolidWorks主窗口在指定时间内没有显示.</param>
        /// <returns></returns>
        public static SldWorks RunSolidWorks(int version, bool visible, bool suppressDialogs = false, bool requireMainWindow = true, int startProcessTimeout = 30, int createWindowTimeout = 15)
        {
            var executablePath = GetSolidWorksExecutablePath(version);

            if (File.Exists(executablePath) == false)
                return null;

            var info = new ProcessStartInfo(executablePath);

            if (suppressDialogs)
                info.Arguments = "/r";

            var process = Process.Start(info);
            SldWorks app = null;
            var t = DateTime.Now;

            while (app == null)
            {
                System.Threading.Thread.Sleep(1000);
                if (Math.Abs(DateTime.Now.Subtract(t).Seconds) > startProcessTimeout)
                    return null;

                if (process != null) app = (SldWorks)GetComObjectFromProcessId(process.Id);
            }

            t = DateTime.Now;
            while (IsRunning(isMainWindowCreated: true) == false)
            {
                System.Threading.Thread.Sleep(1000);
                if (Math.Abs(DateTime.Now.Subtract(t).Seconds) > createWindowTimeout)
                    return null;
            }

            if (visible) return app;
            var frame = (Frame)app.Frame();
            if (frame == null)
                return app;

            return app;
        }

        /// <summary>
        /// 创建一个新的Solidworks并返回实例
        /// </summary>
        /// <param name="solidWorksVersion">SolidWorks的版本年份</param>
        /// <param name="visible"></param>
        /// <param name="suppressDialogs">True 则禁用solidworks弹出消息.</param>
        /// <param name="requireMainWindow">True 表示运行完显示到主窗口</param>
        /// <param name="startProcessTimeout">返回Null 如果SolidWorks在指定时间内没有打开。</param>
        /// <param name="createWindowTimeout">返回Null 如果SolidWorks主窗口在指定时间内没有显示.</param>
        public static SldWorks RunSolidWorks(SolidWorksVersion solidWorksVersion, bool visible, bool suppressDialogs = false, bool requireMainWindow = true, int startProcessTimeout = 30, int createWindowTimeout = 15)
        {
            return RunSolidWorks((int)solidWorksVersion, visible, suppressDialogs, requireMainWindow, startProcessTimeout, createWindowTimeout);
        }

        /// <summary>
        /// 通过进程ID获取COM对象
        /// </summary>
        /// <param name="processId"></param>
        /// <returns></returns>
        private static object GetComObjectFromProcessId(int processId)
        {
            IRunningObjectTable runningObjectTable = null;
            IEnumMoniker monikerEnumerator = null;
            IBindCtx ctx = null;

            var numFetched = new IntPtr();
            var monikers = new IMoniker[1];
            try
            {
                CreateBindCtx(0, out ctx);
                ctx.GetRunningObjectTable(out runningObjectTable);
                runningObjectTable.EnumRunning(out monikerEnumerator);
                monikerEnumerator.Reset();

                while (monikerEnumerator.Next(1, monikers, numFetched) == 0)
                {
                    monikers[0].GetDisplayName(ctx, null, out var runningObjectName);

                    if (runningObjectName.IndexOf(processId.ToString(), StringComparison.Ordinal) == -1) continue;
                    runningObjectTable.GetObject(monikers[0], out var objReturnObject);
                    return objReturnObject;
                }
            }
            finally
            {
                if (runningObjectTable != null)
                    Marshal.ReleaseComObject(runningObjectTable);
                if (monikerEnumerator != null)
                    Marshal.ReleaseComObject(monikerEnumerator);
                if (ctx != null)
                    Marshal.ReleaseComObject(ctx);
            }

            return null;
        }

        /// <summary>
        /// 获取安装的最新solidworks版本
        /// </summary>
        /// <returns></returns>
        public static int GetLatestSolidWorksVersion()
        {
            int[] years = GetSolidWorksYears();

            if (years.Length > 0)
                return years[years.Length - 1] - 1992;
            else
                return -1;
        }

        /// <summary>
        /// 通过注册表的卸载程序位置来获取已经安装的 SolidWorks版本。
        /// </summary>
        /// <returns></returns>
        public static int[] GetSolidWorksYears()
        {
            List<int> versions = new List<int>();
            var registry_key = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";

            using (var key = Registry.LocalMachine.OpenSubKey(registry_key))
            {
                if (key != null)
                    foreach (var subkey_name in key.GetSubKeyNames())
                    {
                        using (var subkey = key.OpenSubKey(subkey_name))
                        {
                            if (subkey == null) continue;
                            var displayName = subkey.GetValue("DisplayName") as string;

                            if (!string.IsNullOrWhiteSpace(displayName))
                            {
                                if (displayName.IndexOf("SOLIDWORKS", StringComparison.OrdinalIgnoreCase) > -1)
                                {
                                    string pattern = @"^SOLIDWORKS (\d{4})";
                                    var regex = new Regex(pattern);

                                    if (regex.IsMatch(displayName))
                                    {
                                        MatchCollection matches = Regex.Matches(displayName, pattern);

                                        foreach (Match match in matches)
                                        {
                                            var versionGroup = match.Groups[1].Value;
                                            var version = int.Parse(versionGroup);
                                            if (!versions.Contains(version))
                                                versions.Add(version);
                                        }
                                    }
                                }
                            }
                        }
                    }
            }

            versions.Sort();
            return versions.ToArray();
        }

        /// <summary>
        /// 返回运行时Dll的路径
        /// </summary>
        /// <param name="s"></param>
        /// <returns></returns>
        public static string GetDllPath(string s = "")
        {
            try
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                UriBuilder uri = new UriBuilder(codeBase);
                string path = Uri.UnescapeDataString(uri.Path);
                return Path.GetDirectoryName(path);
            }
            catch (Exception)
            {
                return "";
            }
        }

    }

    /// <summary>
    /// SolidWorks版本
    /// </summary>
    public enum SolidWorksVersion
    {
        /// <summary>
        /// 默认
        /// </summary>
        Default = -1,

        /// <summary>
        /// SolidWorks2016
        /// </summary>
        SolidWorks2015 = 23,

        /// <summary>
        /// SolidWorks2016
        /// </summary>
        SolidWorks2016 = 24,

        /// <summary>
        /// SolidWorks2017
        /// </summary>
        SolidWorks2017 = 25,

        /// <summary>
        /// SolidWorks2018
        /// </summary>
        SolidWorks2018 = 26,

        /// <summary>
        /// SolidWorks2019
        /// </summary>
        SolidWorks2019 = 27,

        /// <summary>
        /// SolidWorks2020
        /// </summary>
        SolidWorks2020 = 28,

        /// <summary>
        /// SolidWorks2021
        /// </summary>
        SolidWorks2021 = 29,

        /// <summary>
        /// SolidWorks2022
        /// </summary>
        SolidWorks2022 = 30,

        /// <summary>
        /// SolidWorks2023
        /// </summary>
        SolidWorks2023 = 31,
    }
}